using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows;
using GameFramework.Managers;
using Microsoft.Devices;
using Microsoft.Phone.Shell;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;

namespace GameFramework
{
    /// <summary>
    /// The base game class of the framework.
    /// </summary>
    public class BaseGame : Game, IBinarySerializable
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="BaseGame"/> class.
        /// </summary>
        public BaseGame()
        {
            // Add an handler for unhandled exceptions
            Application.Current.UnhandledException += GameUnhandledException;

            // Check if the game is running in the emulator
            IsRunningInEmulator = Microsoft.Devices.Environment.DeviceType == DeviceType.Emulator;

            // Set the global reference to the game
            Global.Game = this;

            // Set the default clear color
            ClearColor = Color.Black;

            // Initialize the graphics and the content root directory
            Graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // Set the default display mode
            SetPortraitMode();

            // Initialize the game input
            Input = new GameInput();
            
            // Inizialize the screen manager
            ScreenManager = new ScreenManager();

            // Frame rate is 30 fps by default for Windows Phone.
            TargetElapsedTime = TimeSpan.FromTicks(333333);

            // Set up application lifecycle event handlers
            PhoneApplicationService.Current.Launching += (o, args) => GameLaunching();
            PhoneApplicationService.Current.Activated += (o, args) => GameActivated();
            PhoneApplicationService.Current.Closing += (o, args) => GameClosing();
            PhoneApplicationService.Current.Deactivated += (o, args) => GameDeactivated();

            InactiveSleepTime = TimeSpan.Zero;
        }

        /// <summary>
        /// Gets the graphics device manager.
        /// </summary>
        public GraphicsDeviceManager Graphics { get; private set; }

        /// <summary>
        /// Gets the game input.
        /// </summary>
        public GameInput Input { get; private set; }

        /// <summary>
        /// Gets a value indicating whether the status will be save and load automatically.
        /// </summary>
        public virtual bool SaveAndLoadStatusAutomatically
        {
            // By default the game framework save and load the status automatically
            get { return true; }
        }

        /// <summary>
        /// Gets or sets the game time
        /// </summary>
        public GameTime GameTime { get; set; }

        /// <summary>
        /// Gets or sets the default clear color.
        /// </summary>
        public Color ClearColor { get; set; }

        /// <summary>
        /// Gets the screen width.
        /// </summary>
        public float ScreenWidth
        {
            get { return GraphicsDevice.Viewport.Width; }
        }

        /// <summary>
        /// Gets the screen height.
        /// </summary>
        public float ScreenHeight
        {
            get { return GraphicsDevice.Viewport.Height; }
        }

        /// <summary>
        /// Gets the coordinates of the screen center.
        /// </summary>
        public Vector2 ScreenCenter
        {
            get { return new Vector2(ScreenWidth / 2, ScreenHeight / 2); }
        }

        /// <summary>
        /// Gets or sets the screen manager.
        /// </summary>
        public ScreenManager ScreenManager { get; set; }

        /// <summary>
        /// Gets or sets the sprite batch.
        /// </summary>
        public SpriteBatch SpriteBatch { get; set; }

        /// <summary>
        /// Gets a value indicating whether the game is running in the emulator
        /// </summary>
        public bool IsRunningInEmulator { get; private set; }

        /// <summary>
        /// Gets a value indicating whether the game is running in trial mode.
        /// </summary>
        public bool IsTrialMode { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating whether the game status is available after loading.
        /// </summary>
        public bool GameStatusAvailable { get; set; }

        /// <summary>
        /// Set the portrait mode.
        /// </summary>
        public void SetPortraitMode()
        {
            Graphics.PreferredBackBufferWidth = 480;
            Graphics.PreferredBackBufferHeight = 800;
            Graphics.SupportedOrientations = DisplayOrientation.Portrait;
        }

        /// <summary>
        /// Set the landscape mode.
        /// </summary>
        public void SetLandscapeMode()
        {
            Graphics.PreferredBackBufferWidth = 800;
            Graphics.PreferredBackBufferHeight = 480;
            Graphics.SupportedOrientations = DisplayOrientation.LandscapeLeft;
        }

        /// <summary>
        /// Vibrate the phone for a specific number of milliseconds.
        /// </summary>
        /// <param name="milliseconds">The number of milliseconds</param>
        public void StartVibration(int milliseconds)
        {
            VibrateController.Default.Start(TimeSpan.FromMilliseconds(milliseconds));           
        }

        /// <summary>
        /// Stop the vibration.
        /// </summary>
        public void StopVibration()
        {
            VibrateController.Default.Stop();
        }

        /// <summary>
        /// If the game is running in trial mode let the user to purchase the full version of the game.
        /// </summary>
        public void ShowMarketplace()
        {
            if (!Guide.IsVisible)
            {
                Guide.ShowMarketplace(PlayerIndex.One);

                // Note: On Windows Phone, this method causes the game to close but not in the emulator.
            }
        }

        /// <summary>
        /// Set the game to run in trial mode.
        /// NOTE: Use this method only for your testing.
        /// </summary>
        public void SimulateTrialMode()
        {
            Guide.SimulateTrialMode = true;
            CheckTrialMode();
        }

        /// <summary>
        /// Serialize the base game status into a stream.
        /// </summary>
        /// <param name="writer">The writer.</param>
        public virtual void Write(BinaryWriter writer)
        {
            ScreenManager.Write(writer);
        }

        /// <summary>
        /// Deserialize the base game status from a stream.
        /// </summary>
        /// <param name="reader">The reader.</param>
        public virtual void Read(BinaryReader reader)
        {
            ScreenManager.Read(reader);
        }

        /// <summary>
        /// Virtual function to allow the game to handle unexpected exceptions.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event arguments.</param>
        protected virtual void GameUnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
        {
            // By default the exception is not handled
            throw e.ExceptionObject;
        }

        /// <summary>
        /// Load the base game content.
        /// </summary>
        protected override void LoadContent()
        {
            base.LoadContent();

            // Create a new SpriteBatch, which can be used to draw textures.
            SpriteBatch = new SpriteBatch(GraphicsDevice);
        }

        /// <summary>
        /// Check if the game is running in trial mode
        /// </summary>
        protected void CheckTrialMode()
        {
            IsTrialMode = Guide.IsTrialMode;
        }

        /// <summary>
        /// Virtual function to allow the game to handle the Launching event
        /// </summary>
        protected virtual void GameLaunching()
        {
            GameStatusAvailable = false;
            CheckTrialMode();

            if (SaveAndLoadStatusAutomatically)
            {
                GameStatusAvailable = LoadGameStatusFromDisk();
            }
        }

        /// <summary>
        /// Virtual function to allow the game to handle the Activated event.
        /// </summary>
        protected virtual void GameActivated()
        {
            GameStatusAvailable = false;
            CheckTrialMode();

            if (SaveAndLoadStatusAutomatically)
            {
                GameStatusAvailable = LoadGameStatusFromDisk();
            }
        }

        /// <summary>
        /// Virtual function to allow the game to handle the Closing event
        /// </summary>
        protected virtual void GameClosing()
        {
            if (SaveAndLoadStatusAutomatically)
            {
                SaveGameStatusToDisk();
            }
        }

        /// <summary>
        /// Virtual function to allow the game to handle the Deactivated event.
        /// </summary>
        protected virtual void GameDeactivated()
        {
            if (SaveAndLoadStatusAutomatically)
            {
                SaveGameStatusToDisk();
            }
        }

        /// <summary>
        /// Save the game status to disk.
        /// </summary>
        protected void SaveGameStatusToDisk()
        {
            try
            {
                using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    using (IsolatedStorageFileStream stream = storage.OpenFile("game.dat", FileMode.Create, FileAccess.Write))
                    {
                        using (var writer = new BinaryWriter(stream))
                        {
                            Write(writer);
                        }
                    }
                }
            }
            catch
            {
                //// If an error occur during the serialization the corrupted storage file will be deleted
                
                try
                {
                    using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
                    {
                        storage.DeleteFile("game.dat");
                    }
                }
                catch
                {
                }
            }
        }

        /// <summary>
        /// Load the game status from disk.
        /// </summary>
        /// <returns>True if success.</returns>
        protected bool LoadGameStatusFromDisk()
        {
            try
            {
                using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    // If the storage file does not exist return failure.
                    if (!storage.FileExists("game.dat"))
                    {
                        return false;
                    }

                    using (IsolatedStorageFileStream stream = storage.OpenFile("game.dat", FileMode.Open, FileAccess.Read))
                    {
                        using (var reader = new BinaryReader(stream))
                        {
                            Read(reader);
                        }
                    }

                    if (ScreenManager.Screens.Count == 0)
                    {
                        return false;
                    }
                }
            }
            catch
            {
                ScreenManager.Screens.Clear();
                return false;
            }

            return true;
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload all unmanaged content.
        /// </summary>
        protected override void UnloadContent()
        {
            Input.DisposeAccelerometer();
        }

        /// <summary>
        /// Update the base game.
        /// </summary>
        /// <param name="gameTime">The game time.</param>
        protected override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            GameTime = gameTime;

            Input.Update();
            ScreenManager.Update();
        }

        /// <summary>
        /// Draw the base game.
        /// </summary>
        /// <param name="gameTime">The game time.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(ClearColor);

            SpriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
            ScreenManager.Draw();
            SpriteBatch.End();
        }
    }
}